@adminforth/markdown 1.10.12 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.log +1 -1
- package/dist/index.js +66 -41
- package/index.ts +32 -13
- package/package.json +1 -1
package/build.log
CHANGED
package/dist/index.js
CHANGED
|
@@ -19,11 +19,24 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
19
19
|
}
|
|
20
20
|
// Placeholder for future Upload Plugin API integration.
|
|
21
21
|
// For now, treat all extracted URLs as plugin-owned public URLs.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
isUrlFromPlugin(url) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
if (!this.uploadPlugin)
|
|
25
|
+
return false;
|
|
26
|
+
try {
|
|
27
|
+
const uploadPlugin = this.uploadPlugin;
|
|
28
|
+
if (typeof uploadPlugin.isInternalUrl === 'function') {
|
|
29
|
+
return yield uploadPlugin.isInternalUrl(url);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw new Error('Please update upload plugin and storage adapter');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.error(`[MarkdownPlugin] Error checking URL ${url}:`, err);
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
});
|
|
27
40
|
}
|
|
28
41
|
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
29
42
|
this.adminforth = adminforth;
|
|
@@ -145,23 +158,33 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
145
158
|
}
|
|
146
159
|
catch (_a) {
|
|
147
160
|
// Fallback: strip scheme/host if it looks like a URL, otherwise treat as a path.
|
|
148
|
-
|
|
161
|
+
const key = stripQueryAndHash(url).replace(/^https?:\/\/[^\/]+\/+/, '').replace(/^\/+/, '');
|
|
162
|
+
// since only local storage adapter returns non-URL paths
|
|
163
|
+
// we need to cut-off express-base
|
|
164
|
+
// e.g. /uploaded-static/cars_description_images-74bbe25a154500c4a2fcb91fb91cea75/sqlite/images_1775114133312.webp
|
|
165
|
+
// should become /sqlite/images_1775114133312.webp
|
|
166
|
+
const cutted = key.split("/").filter(Boolean).slice(2).join("/");
|
|
167
|
+
return cutted;
|
|
149
168
|
}
|
|
150
169
|
};
|
|
151
170
|
const shouldTrackUrl = (url) => {
|
|
152
171
|
try {
|
|
153
|
-
return this.
|
|
172
|
+
return this.isUrlFromPlugin(url);
|
|
154
173
|
}
|
|
155
174
|
catch (err) {
|
|
156
175
|
console.error('Error checking URL ownership', url, err);
|
|
157
176
|
return false;
|
|
158
177
|
}
|
|
159
178
|
};
|
|
160
|
-
const getKeyFromTrackedUrl = (rawUrl) => {
|
|
179
|
+
const getKeyFromTrackedUrl = (rawUrl) => __awaiter(this, void 0, void 0, function* () {
|
|
161
180
|
const srcTrimmed = rawUrl.trim().replace(/^<|>$/g, '');
|
|
162
181
|
if (!srcTrimmed || srcTrimmed.startsWith('data:') || srcTrimmed.startsWith('javascript:')) {
|
|
163
182
|
return null;
|
|
164
183
|
}
|
|
184
|
+
const isInternal = yield this.isUrlFromPlugin(srcTrimmed);
|
|
185
|
+
if (!isInternal) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
165
188
|
if (!shouldTrackUrl(srcTrimmed)) {
|
|
166
189
|
return null;
|
|
167
190
|
}
|
|
@@ -171,7 +194,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
171
194
|
return null;
|
|
172
195
|
}
|
|
173
196
|
return decodeURIComponent(key);
|
|
174
|
-
};
|
|
197
|
+
});
|
|
175
198
|
const upsertMeta = (byKey, key, next) => {
|
|
176
199
|
var _a, _b;
|
|
177
200
|
const existing = byKey.get(key);
|
|
@@ -201,38 +224,40 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
201
224
|
return cleaned || null;
|
|
202
225
|
};
|
|
203
226
|
function getAttachmentMetas(markdown) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// Markdown image syntax:  or  or 
|
|
209
|
-
const imageRegex = /!\[([^\]]*)\]\(\s*([^\s)]+)\s*(?:\s+(?:\"([^\"]*)\"|'([^']*)'))?\s*\)/g;
|
|
210
|
-
// HTML embedded media links.
|
|
211
|
-
const htmlSrcRegex = /<(?:source|video)\b[^>]*\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s"'=<>`]+))[^>]*>/gi;
|
|
212
|
-
const byKey = new Map();
|
|
213
|
-
for (const match of markdown.matchAll(imageRegex)) {
|
|
214
|
-
const altRaw = (_a = match[1]) !== null && _a !== void 0 ? _a : '';
|
|
215
|
-
const srcRaw = match[2];
|
|
216
|
-
const titleRaw = normalizeAttachmentTitleForDb((_c = ((_b = match[3]) !== null && _b !== void 0 ? _b : match[4])) !== null && _c !== void 0 ? _c : null);
|
|
217
|
-
const key = getKeyFromTrackedUrl(srcRaw);
|
|
218
|
-
if (!key) {
|
|
219
|
-
continue;
|
|
227
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
228
|
+
var _a, _b, _c, _d, _e, _f;
|
|
229
|
+
if (!markdown) {
|
|
230
|
+
return [];
|
|
220
231
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
+
// Markdown image syntax:  or  or 
|
|
233
|
+
const imageRegex = /!\[([^\]]*)\]\(\s*([^\s)]+)\s*(?:\s+(?:\"([^\"]*)\"|'([^']*)'))?\s*\)/g;
|
|
234
|
+
// HTML embedded media links.
|
|
235
|
+
const htmlSrcRegex = /<(?:source|video)\b[^>]*\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s"'=<>`]+))[^>]*>/gi;
|
|
236
|
+
const byKey = new Map();
|
|
237
|
+
for (const match of markdown.matchAll(imageRegex)) {
|
|
238
|
+
const altRaw = (_a = match[1]) !== null && _a !== void 0 ? _a : '';
|
|
239
|
+
const srcRaw = match[2];
|
|
240
|
+
const titleRaw = normalizeAttachmentTitleForDb((_c = ((_b = match[3]) !== null && _b !== void 0 ? _b : match[4])) !== null && _c !== void 0 ? _c : null);
|
|
241
|
+
const key = yield getKeyFromTrackedUrl(srcRaw);
|
|
242
|
+
if (!key) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
upsertMeta(byKey, key, {
|
|
246
|
+
alt: altRaw,
|
|
247
|
+
title: titleRaw,
|
|
248
|
+
});
|
|
232
249
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
250
|
+
let srcMatch;
|
|
251
|
+
while ((srcMatch = htmlSrcRegex.exec(markdown)) !== null) {
|
|
252
|
+
const srcRaw = (_f = (_e = (_d = srcMatch[1]) !== null && _d !== void 0 ? _d : srcMatch[2]) !== null && _e !== void 0 ? _e : srcMatch[3]) !== null && _f !== void 0 ? _f : '';
|
|
253
|
+
const key = yield getKeyFromTrackedUrl(srcRaw);
|
|
254
|
+
if (!key) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
upsertMeta(byKey, key, {});
|
|
258
|
+
}
|
|
259
|
+
return [...byKey.values()];
|
|
260
|
+
});
|
|
236
261
|
}
|
|
237
262
|
const createAttachmentRecords = (adminforth, options, recordId, metas, adminUser) => __awaiter(this, void 0, void 0, function* () {
|
|
238
263
|
if (!metas.length) {
|
|
@@ -334,7 +359,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
334
359
|
});
|
|
335
360
|
(resourceConfig.hooks.create.afterSave).push((_a) => __awaiter(this, [_a], void 0, function* ({ record, adminUser }) {
|
|
336
361
|
// find all s3Paths in the html
|
|
337
|
-
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
362
|
+
const metas = yield getAttachmentMetas(record[this.options.fieldName]);
|
|
338
363
|
const keys = metas.map(m => m.key);
|
|
339
364
|
process.env.HEAVY_DEBUG && console.log('📸 Found attachment keys', keys);
|
|
340
365
|
// create attachment records
|
|
@@ -355,7 +380,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
355
380
|
Filters.EQ(this.options.attachments.attachmentResourceIdFieldName, resourceConfig.resourceId)
|
|
356
381
|
]);
|
|
357
382
|
const existingKeys = existingAparts.map((a) => a[this.options.attachments.attachmentFieldName]);
|
|
358
|
-
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
383
|
+
const metas = yield getAttachmentMetas(record[this.options.fieldName]);
|
|
359
384
|
const newKeys = metas.map(m => m.key);
|
|
360
385
|
process.env.HEAVY_DEBUG && console.log('📸 Existing keys (from db)', existingKeys);
|
|
361
386
|
process.env.HEAVY_DEBUG && console.log('📸 Found new keys (from text)', newKeys);
|
package/index.ts
CHANGED
|
@@ -19,11 +19,19 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
19
19
|
|
|
20
20
|
// Placeholder for future Upload Plugin API integration.
|
|
21
21
|
// For now, treat all extracted URLs as plugin-owned public URLs.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
async isUrlFromPlugin(url: string): Promise<boolean> {
|
|
23
|
+
if (!this.uploadPlugin) return false;
|
|
24
|
+
try {
|
|
25
|
+
const uploadPlugin = this.uploadPlugin as any;
|
|
26
|
+
if (typeof uploadPlugin.isInternalUrl === 'function') {
|
|
27
|
+
return await uploadPlugin.isInternalUrl(url);
|
|
28
|
+
} else {
|
|
29
|
+
throw new Error ('Please update upload plugin and storage adapter')
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error(`[MarkdownPlugin] Error checking URL ${url}:`, err);
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
@@ -162,24 +170,35 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
162
170
|
return u.pathname.replace(/^\/+/, '');
|
|
163
171
|
} catch {
|
|
164
172
|
// Fallback: strip scheme/host if it looks like a URL, otherwise treat as a path.
|
|
165
|
-
|
|
173
|
+
const key = stripQueryAndHash(url).replace(/^https?:\/\/[^\/]+\/+/, '').replace(/^\/+/, '');
|
|
174
|
+
|
|
175
|
+
// since only local storage adapter returns non-URL paths
|
|
176
|
+
// we need to cut-off express-base
|
|
177
|
+
// e.g. /uploaded-static/cars_description_images-74bbe25a154500c4a2fcb91fb91cea75/sqlite/images_1775114133312.webp
|
|
178
|
+
// should become /sqlite/images_1775114133312.webp
|
|
179
|
+
const cutted = key.split("/").filter(Boolean).slice(2).join("/");
|
|
180
|
+
return cutted;
|
|
166
181
|
}
|
|
167
182
|
};
|
|
168
183
|
|
|
169
184
|
const shouldTrackUrl = (url: string) => {
|
|
170
185
|
try {
|
|
171
|
-
return this.
|
|
186
|
+
return this.isUrlFromPlugin(url);
|
|
172
187
|
} catch (err) {
|
|
173
188
|
console.error('Error checking URL ownership', url, err);
|
|
174
189
|
return false;
|
|
175
190
|
}
|
|
176
191
|
};
|
|
177
192
|
|
|
178
|
-
const getKeyFromTrackedUrl = (rawUrl: string): string | null => {
|
|
193
|
+
const getKeyFromTrackedUrl = async (rawUrl: string): Promise<string | null> => {
|
|
179
194
|
const srcTrimmed = rawUrl.trim().replace(/^<|>$/g, '');
|
|
180
195
|
if (!srcTrimmed || srcTrimmed.startsWith('data:') || srcTrimmed.startsWith('javascript:')) {
|
|
181
196
|
return null;
|
|
182
197
|
}
|
|
198
|
+
const isInternal = await this.isUrlFromPlugin(srcTrimmed);
|
|
199
|
+
if (!isInternal) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
183
202
|
if (!shouldTrackUrl(srcTrimmed)) {
|
|
184
203
|
return null;
|
|
185
204
|
}
|
|
@@ -225,7 +244,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
225
244
|
return cleaned || null;
|
|
226
245
|
};
|
|
227
246
|
|
|
228
|
-
function getAttachmentMetas(markdown: string): AttachmentMeta[] {
|
|
247
|
+
async function getAttachmentMetas(markdown: string): Promise<AttachmentMeta[]> {
|
|
229
248
|
if (!markdown) {
|
|
230
249
|
return [];
|
|
231
250
|
}
|
|
@@ -242,7 +261,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
242
261
|
const srcRaw = match[2];
|
|
243
262
|
const titleRaw = normalizeAttachmentTitleForDb((match[3] ?? match[4]) ?? null);
|
|
244
263
|
|
|
245
|
-
const key = getKeyFromTrackedUrl(srcRaw);
|
|
264
|
+
const key = await getKeyFromTrackedUrl(srcRaw);
|
|
246
265
|
if (!key) {
|
|
247
266
|
continue;
|
|
248
267
|
}
|
|
@@ -255,7 +274,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
255
274
|
let srcMatch: RegExpExecArray | null;
|
|
256
275
|
while ((srcMatch = htmlSrcRegex.exec(markdown)) !== null) {
|
|
257
276
|
const srcRaw = srcMatch[1] ?? srcMatch[2] ?? srcMatch[3] ?? '';
|
|
258
|
-
const key = getKeyFromTrackedUrl(srcRaw);
|
|
277
|
+
const key = await getKeyFromTrackedUrl(srcRaw);
|
|
259
278
|
if (!key) {
|
|
260
279
|
continue;
|
|
261
280
|
}
|
|
@@ -390,7 +409,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
390
409
|
|
|
391
410
|
(resourceConfig.hooks.create.afterSave).push(async ({ record, adminUser }: { record: any, adminUser: AdminUser }) => {
|
|
392
411
|
// find all s3Paths in the html
|
|
393
|
-
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
412
|
+
const metas = await getAttachmentMetas(record[this.options.fieldName]);
|
|
394
413
|
const keys = metas.map(m => m.key);
|
|
395
414
|
process.env.HEAVY_DEBUG && console.log('📸 Found attachment keys', keys);
|
|
396
415
|
// create attachment records
|
|
@@ -416,7 +435,7 @@ export default class MarkdownPlugin extends AdminForthPlugin {
|
|
|
416
435
|
]);
|
|
417
436
|
const existingKeys = existingAparts.map((a: any) => a[this.options.attachments.attachmentFieldName]);
|
|
418
437
|
|
|
419
|
-
const metas = getAttachmentMetas(record[this.options.fieldName]);
|
|
438
|
+
const metas = await getAttachmentMetas(record[this.options.fieldName]);
|
|
420
439
|
const newKeys = metas.map(m => m.key);
|
|
421
440
|
|
|
422
441
|
process.env.HEAVY_DEBUG && console.log('📸 Existing keys (from db)', existingKeys)
|